﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using Language.Stub;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.Settings;
using Red.Scripts.Defines.Utilities;

namespace Red.Scripts.Options.Tool.Pane.FindSymbol
{
    namespace SearchOptions
    {
        public class Boolean : IVsWindowSearchBooleanOption
        {
            #region Private Data

            private string _displayText;
            private string _toolTip;
            private bool _value;

            #endregion Private Data

            #region C-Tor

            public Boolean( string displayText, string toolTip, bool value )
            {
                _displayText = displayText;
                _toolTip = toolTip;
                _value = value;
            }

            #endregion C-Tor

            #region IVsWindowSearchBooleanOption

            public string DisplayText => _displayText;

            public string Tooltip => _toolTip;

            public bool Value
            {
                get
                {
                    return _value;
                }

                set
                {
                    if ( _value != value )
                    {
                        _value = value;
                        ValueChanged?.Invoke( this, value );
                    }
                }
            }

            #endregion IVsWindowSearchBooleanOption

            #region Events

            public delegate void ValueChangedHandler( Boolean sender, bool value );
            public event ValueChangedHandler ValueChanged;

            #endregion Events
        }
    }

    /// <summary>
    /// This class implements the tool window exposed by this package and hosts a user control.
    /// </summary>
    /// <remarks>
    /// In Visual Studio tool windows are composed of a frame (implemented by the shell) and a pane,
    /// usually implemented by the package implementer.
    /// <para>
    /// This class derives from the ToolWindowPane class provided from the MPF in order to use its
    /// implementation of the IVsUIElementPane interface.
    /// </para>
    /// </remarks>
    [Guid( "5751d91b-47fa-4e2e-8d22-6e146f7d3be8" )]
    public partial class Window : ToolWindowPane, IVsWindowFrameNotify3
    {
        #region Private Data

        private const string SettingsCollectionName = @"Red Scripts\FindSymbol";
        private const string SettingsSearchFlagsName = @"SearchFlags";

        private Database _database;

        #endregion Private Data

        #region Public Properties

        public Database Database
        {
            get { return _database; }
            set
            {
                if ( _database != value )
                {
                    if ( _database != null )
                        _database.Updated -= _database_Updated;

                    _database = value;

                    if ( _database != null )
                    {
                        _database.Updated += _database_Updated;
                        CreateFlatDB();
                    }
                }
            }
        }

        public Control InternalControl { get; private set; }
        public IEnumerable<Symbol> FlatDB { set { InternalControl.FlatDB = value; } }

        #region Search Options

        public SearchOptions.Boolean IncludeParameters { get; private set; } = new SearchOptions.Boolean( "Include function parameters", "Include all parameters from all functions in symbol search", false );
        public SearchOptions.Boolean IncludeLocals { get; private set; } = new SearchOptions.Boolean( "Include function locals", "Include all local variables from all functions in symbol search", true );
        public SearchOptions.Boolean IncludeEnumOptions { get; private set; } = new SearchOptions.Boolean( "Include enum options", "Include the options of all enums as well as the enums themselves", true );

        #endregion Search Options

        #endregion Public Properties

        #region C-Tor

        /// <summary>
        /// Initializes a new instance of the <see cref="FindSymbol"/> class.
        /// </summary>
        public Window()
            : base( null )
        {
            Caption = "Find Symbol";

            // This is the user control hosted by the tool window; Note that, even if this class implements IDisposable,
            // we are not calling Dispose on this object. This is because ToolWindowPane calls Dispose on
            // the object returned by the Content property.
            InternalControl = new Control();
            Content = InternalControl;

            InternalControl.Loaded += InternalControl_Loaded;

            InitializeSearchFlags();
        }

        #endregion C-Tor

        #region Private Methods

        private void InitializeSearchFlags()
        {
            SettingsManager settingsManager = new ShellSettingsManager( ServiceProvider.GlobalProvider );
            SettingsStore userSettingsStore = settingsManager.GetReadOnlySettingsStore( SettingsScope.UserSettings );

            if ( userSettingsStore.PropertyExists( SettingsCollectionName, SettingsSearchFlagsName ) )
            {
                uint data = userSettingsStore.GetUInt32( SettingsCollectionName, SettingsSearchFlagsName, (uint)SearchFlags );
                SearchFlags flags = (SearchFlags)data;

                IncludeParameters.Value = flags.HasFlag( SearchFlags.IncludeParameters );
                IncludeLocals.Value = flags.HasFlag( SearchFlags.IncludeLocals );
                IncludeEnumOptions.Value = flags.HasFlag( SearchFlags.IncludeEnumOptions );
            }

            // Listen to the events after we set the default values
            IncludeParameters.ValueChanged += SearchOption_ValueChanged;
            IncludeLocals.ValueChanged += SearchOption_ValueChanged;
            IncludeEnumOptions.ValueChanged += SearchOption_ValueChanged;
        }

        private void InitializeDatabase()
        {
            if ( Database == null )
            {
                var componentModel = (IComponentModel)ServiceProvider.GlobalProvider.GetService( typeof( SComponentModel ) );
                Database = componentModel.GetService<Database>();

                if ( Database != null )
                {
                    InternalControl.IsVisibleChanged -= InternalControl_IsVisibleChanged;
                }
            }
        }

        private string GetSelection()
        {
            EnvDTE.DTE dte = (EnvDTE.DTE)ServiceProvider.GlobalProvider.GetService( typeof( EnvDTE.DTE ) );

            EnvDTE.TextSelection selection = (EnvDTE.TextSelection)dte.ActiveDocument?.Selection;

            return selection?.Text;
        }

        #endregion Private Methods

        #region Public Methods

        public SearchFlags SearchFlags => SearchFlags.None
            .Add( SearchFlags.IncludeParameters, IncludeParameters.Value )
            .Add( SearchFlags.IncludeLocals, IncludeLocals.Value )
            .Add( SearchFlags.IncludeEnumOptions, IncludeEnumOptions.Value );

        public async void CreateFlatDB()
        {
            if ( Database == null )
                return;

            var copiedDB = Database.FileByPath.ToList();

            SearchFlags searchFlags = SearchFlags;

            FlatDB = await System.Threading.Tasks.Task.Run( () =>
            {
                return from file in copiedDB
                       from stub in file.Value.FlatStubs
                       from flat in stub.Flatten( searchFlags )
                       select flat;
            } );
        }

        #endregion Public Methods

        #region Event Handlers

        private void _database_Updated( object sender, Database.FileUpdatedArgs args )
        {
            CreateFlatDB();
        }

        private void InternalControl_Loaded( object sender, RoutedEventArgs e )
        {
            InternalControl.GoToSymbol += InternalControl_GoToSymbol;
            InternalControl.IsVisibleChanged += InternalControl_IsVisibleChanged;
            InitializeDatabase();
        }

        private void InternalControl_GoToSymbol( Symbol symbol )
        {
            Language.Utility.Goto.Symbol( symbol );
        }

        private void InternalControl_IsVisibleChanged( object sender, DependencyPropertyChangedEventArgs e )
        {
            InitializeDatabase();
        }

        private void SearchOption_ValueChanged( SearchOptions.Boolean sender, bool value )
        {
            SettingsManager settingsManager = new ShellSettingsManager( ServiceProvider.GlobalProvider );
            WritableSettingsStore userSettingsStore = settingsManager.GetWritableSettingsStore( SettingsScope.UserSettings );

            userSettingsStore.CreateCollection( SettingsCollectionName );
            userSettingsStore.SetUInt32( SettingsCollectionName, SettingsSearchFlagsName, (uint)SearchFlags );

            CreateFlatDB();
        }

        #endregion Event Handlers

        #region IVsWindowSearch

        public override bool SearchEnabled => true;

        public override IVsEnumWindowSearchOptions SearchOptionsEnum => new WindowSearchOptionEnumerator( new List<IVsWindowSearchOption>()
        {
            IncludeParameters,
            IncludeLocals,
            IncludeEnumOptions
        } );

        public override IVsSearchTask CreateSearch( uint dwCookie, IVsSearchQuery pSearchQuery, IVsSearchCallback pSearchCallback )
        {
            if ( pSearchQuery == null || pSearchCallback == null || Database == null )
                return null;

            return new SymbolSearchTask( dwCookie, pSearchQuery, pSearchCallback ) { Window = this, Source = Database.FileByPath.Values.ToList() };
        }

        public override void ClearSearch()
        {
            CreateFlatDB();
        }

        public override void ProvideSearchSettings( IVsUIDataSource pSearchSettings )
        {
            Microsoft.Internal.VisualStudio.PlatformUI.Utilities.SetValue( pSearchSettings, SearchSettingsDataSource.SearchStartTypeProperty.Name, (uint)VSSEARCHSTARTTYPE.SST_INSTANT );
        }

        public override bool OnNavigationKeyDown( uint dwNavigationKey, uint dwModifiers )
        {
            switch( ( __VSSEARCHNAVIGATIONKEY)dwNavigationKey )
            {
                case __VSSEARCHNAVIGATIONKEY.SNK_DOWN:
                    ++InternalControl.SymbolGrid.SelectedIndex;
                    InternalControl.SymbolGrid.ScrollIntoView( InternalControl.SymbolGrid.SelectedItem );
                    return true;

                case __VSSEARCHNAVIGATIONKEY.SNK_UP:
                    --InternalControl.SymbolGrid.SelectedIndex;
                    InternalControl.SymbolGrid.ScrollIntoView( InternalControl.SymbolGrid.SelectedItem );
                    return true;

                case __VSSEARCHNAVIGATIONKEY.SNK_ENTER:
                    if( InternalControl.SymbolGrid.SelectedItem != null )
                    {
                        InternalControl_GoToSymbol( (Symbol)InternalControl.SymbolGrid.SelectedItem );
                    }
                    return true;

                case __VSSEARCHNAVIGATIONKEY.SNK_PAGEDOWN:
                    {
                        ScrollViewer scrollViewer = Wpf.GetVisualChild<ScrollViewer>( InternalControl );
                        scrollViewer.PageDown();
                    }
                    return true;

                case __VSSEARCHNAVIGATIONKEY.SNK_PAGEUP:
                    {
                        ScrollViewer scrollViewer = Wpf.GetVisualChild<ScrollViewer>( InternalControl );
                        scrollViewer.PageUp();
                    }
                    return true;
            }

            return false;
        }

        #endregion IVsWindowSearch

        #region IVsWindowFrameNotify3

        public int OnShow( int fShow )
        {
            if ( fShow != 0 )
            {
                SearchHost.Activate();

                // Grab any text that's selected in the main view and search for that by default
                string text = GetSelection();
                if( !string.IsNullOrWhiteSpace( text ) )
                {
                    IVsSearchQuery query = SearchUtilities.CreateSearchQuery( text );
                    SearchHost.SearchAsync( query );
                }
            }


            return VSConstants.S_OK;
        }

        public int OnMove( int x, int y, int w, int h )
        {
            return VSConstants.S_OK;
        }

        public int OnSize( int x, int y, int w, int h )
        {
            return VSConstants.S_OK;
        }

        public int OnDockableChange( int fDockable, int x, int y, int w, int h )
        {
            return VSConstants.S_OK;
        }

        public int OnClose( ref uint pgrfSaveOptions )
        {
            return VSConstants.S_OK;
        }

        #endregion IVsWindowFrameNotify3
    }

    internal class SymbolSearchTask : VsSearchTask
    {
        #region Public Properties

        public Window Window { get; set; }
        public IEnumerable<File> Source { get; set; }

        #endregion Public Properties

        #region C-Tor

        public SymbolSearchTask( uint dwCookie, IVsSearchQuery pSearchQuery, IVsSearchCallback pSearchCallback )
            : base( dwCookie, pSearchQuery, pSearchCallback )
        {
        }

        #endregion C-Tor

        #region VsSearchTask

        protected override void OnStartSearch()
        {
            string searchString = SearchQuery.SearchString;

            SearchFlags searchFlags = Window.SearchFlags;

            var results = from file in Source
                          from stub in file.FlatStubs
                          from flat in stub.Flatten( searchFlags )
                          where flat.Name.IndexOf( searchString, StringComparison.OrdinalIgnoreCase ) != -1
                          select flat;

            ThreadHelper.Generic.Invoke( () =>
            {
                Window.FlatDB = results;
            } );

            // Call the implementation of this method in the base class.   
            // This sets the task status to complete and reports task completion.   
            base.OnStartSearch();
        }

        #endregion VsSearchTask
    }
}
